﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Globalization;
using System.IO;
using System.Xml;

namespace Xceed.Silverlight.DataGrid.Export
{
  internal class ExporterWriter
  {
    internal ExporterWriter()
    {
    }

    internal ExporterWriter( CsvFormatSettings formatSettings )
    {
      m_formatSettings = formatSettings;
    }

    internal void XmlssStartExport( Stream baseStream )
    {
      XmlWriterSettings settings = new XmlWriterSettings();

      settings.CloseOutput = false;
      settings.Indent = true;

      m_xmlWriter = XmlWriter.Create( baseStream, settings );

      m_xmlWriter.WriteProcessingInstruction( "mso-application", "progid=" + '"' + "Excel.Sheet" + '"' );
      m_xmlWriter.WriteStartElement( "Workbook", "urn:schemas-microsoft-com:office:spreadsheet" );
      m_xmlWriter.WriteAttributeString( "xmlns", "o", null, "urn:schemas-microsoft-com:office:office" );
      m_xmlWriter.WriteAttributeString( "xmlns", "x", null, "urn:schemas-microsoft-com:office:excel" );
      m_xmlWriter.WriteAttributeString( "xmlns", "ss", null, "urn:schemas-microsoft-com:office:spreadsheet" );
      m_xmlWriter.WriteAttributeString( "xmlns", "html", null, "http://www.w3.org/TR/REC-html40" );

      //Set the format of DateTime fields.
      this.WriteXmlssStyles();

      m_xmlWriter.WriteStartElement( "Worksheet" );
      m_xmlWriter.WriteAttributeString( "ss", "Name", null, "Sheet1" );

      m_xmlWriter.WriteStartElement( "Table" );
    }

    internal void XmlssStopExport( bool fixedHeader, bool fixedColumn, int fixedColumnCount )
    {
      // Table
      m_xmlWriter.WriteEndElement();

      // WorksheetOptions
      if( fixedHeader || fixedColumn )
      {
        m_xmlWriter.WriteStartElement( "WorksheetOptions", "urn:schemas-microsoft-com:office:excel" );
        m_xmlWriter.WriteElementString( "FreezePanes", "urn:schemas-microsoft-com:office:excel", null );
        m_xmlWriter.WriteElementString( "FrozenNoSplit", "urn:schemas-microsoft-com:office:excel", null );

        if( fixedHeader )
        {
          m_xmlWriter.WriteStartElement( "SplitHorizontal", "urn:schemas-microsoft-com:office:excel" );
          m_xmlWriter.WriteValue( 1 );
          m_xmlWriter.WriteEndElement();
          m_xmlWriter.WriteStartElement( "TopRowBottomPane", "urn:schemas-microsoft-com:office:excel" );
          m_xmlWriter.WriteValue( 1 );
          m_xmlWriter.WriteEndElement();
        }

        if( fixedColumn )
        {
          m_xmlWriter.WriteStartElement( "SplitVertical", "urn:schemas-microsoft-com:office:excel" );
          m_xmlWriter.WriteValue( fixedColumnCount );
          m_xmlWriter.WriteEndElement();
          m_xmlWriter.WriteStartElement( "LeftColumnRightPane", "urn:schemas-microsoft-com:office:excel" );
          m_xmlWriter.WriteValue( fixedColumnCount );
          m_xmlWriter.WriteEndElement();
        }

        // ActivePane
        m_xmlWriter.WriteStartElement( "ActivePane", "urn:schemas-microsoft-com:office:excel" );

        if( fixedColumn && fixedHeader )
        {
          m_xmlWriter.WriteValue( 0 );
        }
        else if( fixedColumn )
        {
          m_xmlWriter.WriteValue( 1 );
        }
        else if( fixedHeader )
        {
          m_xmlWriter.WriteValue( 2 );
        }

        // ActivePane
        m_xmlWriter.WriteEndElement();
        // WorksheetOptions
        m_xmlWriter.WriteEndElement();
      }

      // Worksheet
      m_xmlWriter.WriteEndElement();
      // Workbook
      m_xmlWriter.WriteEndElement();
      m_xmlWriter.Close();

      //Release resources.
      m_xmlWriter = null;
    }

    internal void XmlssStartHeader()
    {
      this.StartXmlssRow();
    }

    internal void XmlssStopHeader()
    {
      //Close row
      this.StopXmlssElement();
    }

    internal void XmlssStartHeaderField( string headerValue )
    {
      m_xmlWriter.WriteStartElement( "Cell" );
      this.WriteXmlssData( headerValue );
    }

    internal void XmlssStopHeaderField()
    {
      //Close cell
      this.StopXmlssElement();
    }

    internal void XmlssStartGroup( string formatedGroupHeaderValue )
    {
      this.StartXmlssRow();
      m_xmlWriter.WriteStartElement( "Cell" );
      this.WriteXmlssData( formatedGroupHeaderValue );
    }

    internal void XmlssStopGroup()
    {
      //Close cell
      m_xmlWriter.WriteEndElement();
      //Close row
      this.StopXmlssElement();
    }

    internal void XmlssStartDataItem()
    {
      this.StartXmlssRow();
    }

    internal void XmlssStopDataItem()
    {
      //Close row
      this.StopXmlssElement();
    }

    internal void XmlssStartDataItemField( int fieldIndex, object fieldValue )
    {
      if( ( fieldValue == null ) || ( Convert.IsDBNull( fieldValue ) ) || ( fieldValue is Array ) )
        return;

      m_xmlWriter.WriteStartElement( "Cell" );

      //This makes sure to skip xmlss cells if there are null field values in the data item.
      if( fieldIndex > m_lasWrittenFieldIndex + 1 )
        m_xmlWriter.WriteAttributeString( "ss", "Index", null, ( fieldIndex + 1 ).ToString( CultureInfo.InvariantCulture ) );

      m_lasWrittenFieldIndex = fieldIndex;

      //Set a reference to the format written at the beginning of the file.
      if( fieldValue is DateTime )
        m_xmlWriter.WriteAttributeString( "ss", "StyleID", null, "xcdDateTime" );

      this.WriteXmlssData( fieldValue );
    }

    internal void XmlssStopDataItemField( object fieldValue )
    {
      //Make sure not to write an end element if there was no start element.
      if( ( fieldValue == null ) || ( Convert.IsDBNull( fieldValue ) ) || ( fieldValue is Array ) )
        return;

      //Close cell
      m_xmlWriter.WriteEndElement();
    }

    internal void CsvStartExport( Stream baseStream )
    {
      m_baseStream = baseStream;

      m_firstField = true;
      m_lasWrittenFieldIndex = -1;
    }

    internal void CsvStopExport()
    {
      m_baseStream = null;
    }

    internal void CsvStopHeader()
    {
      this.WriteCsvNewLine();
      m_firstField = true;
    }

    internal void CsvStartHeaderField( string headerValue )
    {
      if( m_firstField )
      {
        m_firstField = false;
      }
      else
      {
        this.WriteCsvString( m_formatSettings.Separator.ToString() );
      }

      //Format the field value.
      string outputString = FormatHelper.FormatCsvData( typeof( string ), headerValue, m_formatSettings );

      if( !string.IsNullOrEmpty( outputString ) )
        this.WriteCsvString( outputString );
    }

    internal void CsvStartDataItem()
    {
      m_dataRowClosed = false;
    }

    internal void CsvStopDataItem()
    {
      if( !m_dataRowClosed )
      {
        this.WriteCsvNewLine();
        m_firstField = true;
        m_lasWrittenFieldIndex = -1;
        m_dataRowClosed = true;
      }
    }

    internal void CsvStartDataItemField( int fieldIndex, object fieldValue )
    {
      if( m_firstField )
      {
        m_firstField = false;
      }
      else
      {
        this.WriteCsvString( m_formatSettings.Separator.ToString() );
      }

      //This makes sure to skip xmlss cells if there are null field values in the data item.
      for( int i = m_lasWrittenFieldIndex + 1; i < fieldIndex; i++ )
      {
        this.WriteCsvString( m_formatSettings.Separator.ToString() );
      }

      m_lasWrittenFieldIndex = fieldIndex;

      //Format the field value.
      string outputString = FormatHelper.FormatCsvData( null, fieldValue, m_formatSettings );

      if( !string.IsNullOrEmpty( outputString ) )
        this.WriteCsvString( outputString );
    }

    private void StartXmlssRow()
    {
      m_xmlWriter.WriteStartElement( "Row" );
      m_lasWrittenFieldIndex = -1;
    }

    private void StopXmlssElement()
    {
      m_xmlWriter.WriteEndElement();
    }

    private void WriteXmlssStyles()
    {
      m_xmlWriter.WriteStartElement( "Styles" );

      //This will be used to format DateTime fields.
      m_xmlWriter.WriteStartElement( "Style" );
      m_xmlWriter.WriteAttributeString( "ss", "ID", null, "xcdDateTime" );

      m_xmlWriter.WriteStartElement( "NumberFormat" );
      m_xmlWriter.WriteAttributeString( "ss", "Format", null, "General Date" );
      m_xmlWriter.WriteEndElement();

      m_xmlWriter.WriteEndElement();

      m_xmlWriter.WriteEndElement();
    }

    private void WriteXmlssData( object value )
    {
      if( value == null )
        return;

      Type dataType = value.GetType();

      if( ( dataType.IsGenericType ) && ( dataType.GetGenericTypeDefinition() == typeof( Nullable<> ) ) )
      {
        dataType = Nullable.GetUnderlyingType( dataType );
      }

      //Do not export a date smaller then what is supported by xmlss.
      if( ( dataType == typeof( DateTime ) ) && ( ( ( DateTime )value ) < ExporterWriter.MinDateValue ) )
        return;

      m_xmlWriter.WriteStartElement( "Data" );

      if( ( dataType == typeof( int ) ) ||
          ( dataType == typeof( double ) ) ||
          ( dataType == typeof( decimal ) ) ||
          ( dataType == typeof( float ) ) ||
          ( dataType == typeof( short ) ) ||
          ( dataType == typeof( Single ) ) ||
          ( dataType == typeof( UInt16 ) ) ||
          ( dataType == typeof( UInt32 ) ) ||
          ( dataType == typeof( UInt64 ) ) ||
          ( dataType == typeof( Int16 ) ) ||
          ( dataType == typeof( Int64 ) ) )
      {
        m_xmlWriter.WriteAttributeString( "ss", "Type", null, "Number" );
        m_xmlWriter.WriteValue( value );
      }
      else if( dataType == typeof( DateTime ) )
      {
        m_xmlWriter.WriteAttributeString( "ss", "Type", null, "DateTime" );

        // Can't use the XmlWriter's WriteValue overload taking a date because xmlss does
        // not support the time zone indicator unlike standard XML.
        // Ensure to escape the separator characters else the CurrentCulture parameters 
        // will be used, and those may differ from the one expected by the xml format
        m_xmlWriter.WriteValue( ( ( DateTime )value ).ToString( "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'FFFFFFF" ) );
      }
      else if( dataType == typeof( bool ) )
      {
        m_xmlWriter.WriteAttributeString( "ss", "Type", null, "Boolean" );

        //Convert bool to int.
        if( ( bool )value )
        {
          m_xmlWriter.WriteValue( 1 );
        }
        else
        {
          m_xmlWriter.WriteValue( 0 );
        }
      }
      //If the value is an exception
      else if( typeof( Exception ).IsAssignableFrom( dataType ) )
      {
        m_xmlWriter.WriteAttributeString( "ss", "Type", null, "Error" );

        if( dataType == typeof( DivideByZeroException ) )
        {
          m_xmlWriter.WriteValue( "#DIV/0!" );
        }
        else if( dataType == typeof( OverflowException ) )
        {
          m_xmlWriter.WriteValue( "#NUM!" );
        }
        else
        {
          m_xmlWriter.WriteValue( "#N/A" );
        }
      }
      else
      {
        m_xmlWriter.WriteAttributeString( "ss", "Type", null, "String" );

        string stringValue = value.ToString();
        m_xmlWriter.WriteValue( stringValue != null ? stringValue : string.Empty );
      }

      m_xmlWriter.WriteEndElement();
    }

    private void WriteCsvNewLine()
    {
      this.WriteCsvString( m_formatSettings.NewLine );
    }

    private void WriteCsvString( string outputString )
    {
      if( outputString == null )
        return;

      byte[] bytes = System.Text.Encoding.Unicode.GetBytes( outputString );

      m_baseStream.Write( bytes, 0, bytes.Length );
    }

    private XmlWriter m_xmlWriter;
    private int m_lasWrittenFieldIndex;
    private bool m_firstField;
    private bool m_dataRowClosed;
    private CsvFormatSettings m_formatSettings;
    private Stream m_baseStream;

    private static readonly DateTime MinDateValue = new DateTime( 1900, 01, 01 );
  }
}
